/*
 *    Copyright 2012 Thomas Broyer <t.broyer@ltgt.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hupa.client.mapper;

import com.google.gwt.activity.shared.Activity;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.ui.AcceptsOneWidget;

/**
 * Makes it easy to put any {@link Activity} behind a split-point.
 * <p>
 * For simple activities, that could be done using an
 * {@link com.google.gwt.user.client.AsyncProxy AsyncProxy}, but it's not
 * possible for activities created through factories or via dependency
 * injection.
 * <p>
 * As a bonus, the {@link Activity} won't be instantiated at all in case the
 * {@link ActivityAsyncProxy} is {@link #onCancel cancelled} before the
 * {@link GWT.runAsync async call} returns.
 */
public abstract class ActivityAsyncProxy implements Activity {

	private boolean hasAsyncBeenIssued;
	private boolean hasAsyncBeenCancelled;
	private boolean hasAsyncFailed;
	private AcceptsOneWidget display;
	private EventBus eventBus;
	private Activity instance;

	@Override
	public String mayStop() {
		checkHasAsyncFailed();
		assert this.instance != null;
		return this.instance.mayStop();
	}

	@Override
	public void onCancel() {
		if (this.instance != null) {
			this.instance.onCancel();
		}
		checkHasAsyncFailed();
		this.hasAsyncBeenCancelled = true;
	}

	@Override
	public void onStop() {
		checkHasAsyncFailed();
		assert this.instance != null;
		this.instance.onStop();
	}

	@Override
	public void start(AcceptsOneWidget panel, EventBus eventBus) {
		if (this.instance != null) {
			this.instance.start(panel, eventBus);
			return;
		}
		checkHasAsyncFailed();
		assert !this.hasAsyncBeenIssued || this.hasAsyncBeenCancelled;
		this.display = panel;
		this.eventBus = eventBus;
		this.hasAsyncBeenCancelled = false;
		if (!this.hasAsyncBeenIssued) {
			this.hasAsyncBeenIssued = true;
			doAsync(new RunAsyncCallback() {

				@Override
				public void onSuccess() {
					if (!ActivityAsyncProxy.this.hasAsyncBeenCancelled) {
						assert ActivityAsyncProxy.this.instance == null;
						ActivityAsyncProxy.this.instance = createInstance();
						ActivityAsyncProxy.this.instance.start(
								ActivityAsyncProxy.this.display,
								ActivityAsyncProxy.this.eventBus);
					}
				}

				@Override
				public void onFailure(Throwable reason) {
					ActivityAsyncProxy.this.hasAsyncFailed = true;
					if (GWT.getUncaughtExceptionHandler() != null) {
						GWT.getUncaughtExceptionHandler().onUncaughtException(
								reason);
					}
				}
			});
		}
	}

	/**
	 * Implementors should simply call {@link GWT#runAsync} here, and nothing
	 * else.
	 * <p>
	 * This is required to have a different split-point generated for each
	 * {@link ActivityAsyncProxy} sub-class.
	 */
	protected abstract void doAsync(RunAsyncCallback callback);

	protected abstract Activity createInstance();

	private void checkHasAsyncFailed() {
		if (this.hasAsyncFailed) {
			throw new IllegalStateException("runAsync load previously failed");
		}
	}
}
